<?php
class JdApi
{
    const VER = '2.0';
    const TIMEOUT = 30;
    const FMT_DATE = 'Y-m-d H:i:s';
    const FMT_RESPONSE = 'json';
    
    private static $domain = 'api.jd.com/routerjson';
    private static $oauth = 'https://open-oauth.jd.com/oauth2';

    private static $appkey;
    private static $secret;
    private static $token;
    public static $refreshToken;

    private static $ssl;

    private static $instance;


    // public function __construct($appkey, $secret, $token = '', $ssl = true)
    // {
    //     $this->appkey = $appkey;
    //     $this->secret = $secret;
    //     $this->ssl = $ssl;
    //     $this->token = $token;
    // }
    private function __construct($appkey, $secret, $token = '', $ssl = true)
    {
        self::init($appkey, $secret, $token, $ssl);
        // self::init([$appkey, $secret, $token, $ssl]);
    }

    private function __clone() {}

    /**
     * Get instance
     * 
     * @author Eddie
     * 
     * @return Object
     */
    public static function getInstance($appkey = '', $secret = '', $token = '', $ssl = true)
    {
        if (! self::$instance instanceof self) self::$instance = new self($appkey, $secret, $token, $ssl);
        return self::$instance;
    }

    /**
     * Initialization
     * 
     * @author Eddie
     * 
     * @param mixed
     * @return void
     */
    // public static function init($appkey, $secret, $token = '', $ssl = true)
    // {
    //     self::$appkey = $appkey;
    //     self::$secret = $secret;
    //     if (!empty($token)) self::$token = $token;
    //     self::$ssl = (bool)$ssl;
    // }
    public static function init()
    {
        switch(func_num_args()) {
            case 1: // Like: init([$appkey, $secret, $token, $ssl])
                $args = func_get_args()[0];
                if (is_array($args)) call_user_func_array([self::class, 'init'], array_values($args));
                break;
            case 2: // Like: init($appkey, $secret)
                list($appkey, $secret) = func_get_args();
                self::$appkey = $appkey;
                self::$secret = $secret;
                break;
            case 4: // Like: init($appkey, $secret, $token, $ssl)
                list($appkey, $secret, $token, $ssl) = func_get_args();
                self::$appkey = $appkey;
                self::$secret = $secret;
                if (!empty($token)) self::$token = $token;
                self::$ssl = (bool)$ssl;
                break;
        }
    }

    public static function printInstance()
    {
        echo PHP_EOL . print_r([
            'domai' => self::$domain,
            'oauth' => self::$oauth,
            'appkey' => self::$appkey,
            'secret' => self::$secret,
            'token' => self::$token,
            'refresh_token' => self::$refreshToken,
            'ssl' => self::$ssl,
            'instance' => self::$instance,
        ], true) . PHP_EOL;
    }


    #============================== 店铺API start ==============================#
    /**
     * 根据pin查询用户信息
     *
     * @author Eddie
     *
     * @param string $pin
     * @return array|null
     */
    public function findUser($pin)
    {
        return $this->callApi('jingdong.vender.auth.findUser', ['pin'=>$pin]);
    }

    /**
     * 查询商家基本信息
     *
     * @author Eddie
     *
     * @param array|string $param
     * @return mixed|null
     */
    public function getSellerInfo($param)
    {
        if (is_array($param)) $param = json_encode($param);
        return $this->callApi('jingdong.seller.vender.info.get', ['ext_json_param'=>$param]);
    }

    /**
     * 店铺信息查询
     *
     * @author Eddie
     *
     * @return mixed|null
     */
    public function queryShop()
    {
        return $this->callApi('jingdong.vender.shop.query');
    }

    /**
     * 查询退货地址列表
     *
     * @author Eddie
     *
     * @param int $type
     * @return mixed|null
     */
    public function queryReturnAddress($type)
    {
        return $this->callApi('jingdong.vender.returnaddress.query', ['address_type'=>$type]);
    }

    /**
     * 查询发货地址列表
     *
     * @author Eddie
     *
     * @return mixed|null
     */
    public function queryShipAddress()
    {
        return $this->callApi('jingdong.vender.shipaddress.query');
    }

    /**
     * 获取指定的店内分类
     *
     * @author Eddie
     *
     * @param int $cid
     * @return mixed|null
     */
    public function getShopCategoryByCid($cid)
    {
        return $this->callApi('jingdong.vender.shopcategory.getShopCategoryByCid', ['cid'=>$cid]);
    }

    /**
     * 删除指定店内分类
     *
     * @author Eddie
     *
     * @param int $cid
     * @return mixed|null
     */
    public function deleteShopCategoryByCid($cid)
    {
        return $this->callApi('jingdong.vender.shopcategory.deleteShopCategoryByVenderIdAndCid', ['cid'=>$cid]);
    }

    /**
     * 获取商家所有的店内分类
     *
     * @author Eddie
     *
     * @return mixed|null
     */
    public function findShopCategories()
    {
        return $this->callApi('jingdong.vender.shopcategory.findShopCategoriesByVenderId');
    }

    /**
     * 获取指定分类的子分类列表
     *
     * @author Eddie
     *
     * @param int $cid
     * @return mixed|null
     */
    public function findSubShopCategoriesByParentCid($cid)
    {
        return $this->callApi('jingdong.vender.shopcategory.findSubShopCategoriesByParentCid', ['parent_cid'=>$cid]);
    }

    /**
     * 根据登录pin查询商家基本信息
     *
     * @author Eddie
     *
     * @param array $param
     * @return mixed|null
     */
    public function queryByPin($param = [])
    {
        if ($param) $param = ['ext_json_param' => $param];
        return $this->callApi('jingdong.vender.info.queryByPin', $param);
    }

    /**
     * 根据商家编号获取商家基本信息
     *
     * @author Eddie
     *
     * @param int $source
     * @param array $colNames
     * @return mixed|null
     */
    public function getBasicVenderInfoByVenderId($source, $colNames = [])
    {
        $param = ['source' => $source];
        if ($colNames) $param['colNames'] = $colNames;
        return $this->callApi('jingdong.vender.vbinfo.getBasicVenderInfoByVenderId', $param);
    }

    /**
     * 获取商家可用的经营分类列表
     *
     * @author Eddie
     *
     * @return mixed|null
     */
    public function getCategory()
    {
        return $this->callApi('jingdong.vender.category.getValidCategoryResultByVenderId');
    }

    /**
     * 根据商家编号获取店内分类
     *
     * @author Eddie
     *
     * @return mixed|null
     */
    public function getShopCategories()
    {
        return $this->callApi('jingdong.vender.shopcategory.getShopCategorysByVenderId');
    }

    /**
     * 添加ISV活动
     *
     * @author Eddie
     *
     * @param array $data
     * @return mixed|null
     */
    public function addIsvActivity(array $data)
    {
        return $this->callApi('jingdong.promo.unit.pre.addIsvActivity', $data);
    }
    #=============================== 店铺API end ===============================#


    #============================== 商品API start ==============================#
    /**
     * 发布商品
     *
     * @author Eddie
     *
     * @param array $ware
     * @param array $skus
     * @return mixed|null
     */
    public function addWareWrite(array $ware, array $skus = [])
    {
        $param = ['ware' => $ware, 'skus' => $skus];
        return $this->callApi('jingdong.ware.write.add', $param);
    }

    #=============================== 商品API end ===============================#

    #============================== 类目API start ==============================#
    /**
     * 获取新属性的可选值列表
     * 
     * @author Eddie
     * 
     * @param int $attrId
     * @param array $field
     * @return mixed|null
     */
    public function getCategoryValuesByAttributeId($attrId, $field = [])
    {
        $param = ['categoryAttrId' => $attrId];
        if (!empty($field)) $param['field'] = $field;
        return $this->callApi('jingdong.category.read.findValuesByAttrIdUnlimit', $param);
    }

    /**
     * 通过类目和属性类型，获取类目的属性列表 (不限制等级)
     * 
     * @author Eddie
     * 
     * @param int $attrId
     * @param int $type 属性类型: 1->关键属性, 3->可变属性, 4->销售属性
     * @param array $field
     * @return mixed|null
     */
    public function getCategoryAttributesByCategoryId($cid, $type = 0, $field = [])
    {
        $param = ['cid' => $cid];
        if ((int)$type > 0) $param['attributeType'] = $type;
        if (!empty($field)) $param['field'] = $field;
        return $this->callApi('jingdong.category.read.findAttrsByCategoryIdUnlimitCate', $param);
    }
    #=============================== 类目API end ===============================#

    /**
     * 请求接口
     *
     * @author Eddie
     *
     * @param string $method
     * @param array $param
     * @return mixed
     */
    private function callApi($method, $param = [])
    {
        if ($param) $param = ['360buy_param_json' => json_encode($param)];
        $data = array_merge($param, [
            'method' => $method,
            'access_token' => self::$token,
            'app_key' => self::$appkey,
            'timestamp' => $this->getDatetime()->format(self::FMT_DATE),
            'v' => self::VER,
            // 'format' => self::FMT_RESPONSE,
        ]);
        $data['sign'] = $this->sign($data);
        // print_r($data);
        $res = $this->request($data);
        // print_r($res);

        $field = str_replace('.', '_', $method).'_responce';
        if (isset($res[$field])) {
            $res = $res[$field];
        } else if (isset($res['error_response'])) {
            $res = $res['error_response'];
        }
        return $res;
    }


    /**
     * 生成“获取授权码”地址
     *
     * @author Eddie
     *
     * @param string $redirectTo
     * @param string $state
     * @return string
     */
    public function getLoginUrl($redirectTo, $state = '')
    {
        $param = [
            'app_key' => self::$appkey,
            'response_type' => 'code',
            'redirect_uri' => $redirectTo,
            'scope' => 'snsapi_base', //snsapi_base|snsapi_union_login
        ];
        if ($state) $param['state'] = $state;

        return self::$oauth.'/to_login?'. http_build_query($param);
    }

    /**
     * 通过code获取access_token
     *
     * @author Eddie
     *
     * @param string $code
     * @return string
     */
    public function getAccessToken($code)
    {
        $param = [
            'app_key' => self::$appkey,
            'app_secret' => self::$secret,
            'grant_type' => 'authorization_code',
            'code' => $code,
        ];
        $url = self::$oauth.'/access_token?' . http_build_query($param);

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CONNECTTIMEOUT => self::TIMEOUT,
            CURLOPT_TIMEOUT => self::TIMEOUT,
        ]);
        try {
            $ret = curl_exec($ch);
            if ($ret === false) throw new \Exception(curl_error($ch), curl_errno($ch));
            $ret = json_decode($ret, true);

            if (isset($ret['access_token'])) self::$token = $ret['access_token'];

            return $ret;
        } catch (\Exception $e) {
            return [
                'err_no' => $e->getCode(),
                'err_msg' => $e->getMessage(),
            ];
        } finally {
            curl_close($ch);
        }
    }

    /**
     * 刷新授权有效期
     *
     * @author Eddie
     *
     * @param string $refreshToken
     * @return array|bool|mixed|string
     */
    public function refreshToken($refreshToken)
    {
        $param = [
            'app_key' => self::$appkey,
            'app_secret' => self::$secret,
            'grant_type' => 'refresh_token',
            'refresh_token' => $refreshToken,
        ];
        $url = self::$oauth.'/refresh_token?' . http_build_query($param);

        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL => $url,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CONNECTTIMEOUT => self::TIMEOUT,
            CURLOPT_TIMEOUT => self::TIMEOUT,
        ]);
        try {
            $ret = curl_exec($ch);
            if ($ret === false) throw new \Exception(curl_error($ch), curl_errno($ch));
            $ret = json_decode($ret, true);

            if (isset($ret['access_token'])) self::$token = $ret['access_token'];

            return $ret;
        } catch (\Exception $e) {
            return [
                'err_no' => $e->getCode(),
                'err_msg' => $e->getMessage(),
            ];
        } finally {
            curl_close($ch);
        }
    }


    /**
     * Get domain
     *
     * @author Eddie
     *
     * @return string
     */
    public function getDomain()
    {
        return (self::$ssl ? 'https://' : 'http://') . self::$domain;
    }

    /**
     * 获取当前服务器时间 (返回 DateTime 对象)
     *
     * @author Eddie
     *
     * @param string $timezone
     * @return DateTime
     * @throws Exception
     */
    private function getDatetime($timezone = 'PRC')
    {
        return new DateTime('now', new DateTimeZone($timezone));
    }

    /**
     * Make a sign
     *
     * @author Eddie
     *
     * @param array $data
     * @return string
     */
    private function sign(array $data)
    {
        ksort($data);

        $str = '';
        foreach ($data as $k => $v) {
            if (is_array($v)) $v = json_encode($v);
            $str .= $k.$v;
        }
        return strtoupper(md5(self::$secret . $str . self::$secret));
    }

    /**
     * Send request
     *
     * @author Eddie
     *
     * @param array $data
     * @return array
     */
    private function request(array $data)
    {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $this->getDomain());
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::TIMEOUT);
        curl_setopt($ch, CURLOPT_TIMEOUT, self::TIMEOUT);
        curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);

        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));

        try {
            $ret = curl_exec($ch);
            if ($ret === false) throw new \Exception(curl_error($ch), curl_errno($ch));
            $ret = json_decode($ret, true);
            return $ret;
        } catch (\Exception $e) {
            return [
                'err_code' => $e->getCode(),
                'err_msg' => $e->getMessage(),
            ];
        } finally {
            curl_close($ch);
        }
    }
}